package com.xiyu.service.service.setting;

import cn.hutool.core.date.DateUtil;
import com.xiyu.service.enums.system.notice.SystemNoticeTypeEnum;
import com.xiyu.service.model.customer.info.CustomerInfo;
import com.xiyu.service.model.customer.pool.*;
import com.xiyu.service.model.setting.global.SettingGlobal;
import com.xiyu.service.model.system.dept.SystemUserPost;
import com.xiyu.service.model.system.notice.SystemNotice;
import com.xiyu.service.model.system.notice.SystemNoticeDraft;
import com.xiyu.service.model.system.user.SystemUser;
import com.xiyu.service.repository.customer.info.CustomerInfoRepository;
import com.xiyu.service.repository.customer.pool.*;
import com.xiyu.service.repository.setting.global.SettingGlobalRepository;
import com.xiyu.service.repository.system.dept.SystemUserPostRepository;
import com.xiyu.service.repository.system.notice.SystemNoticeRepository;
import com.xiyu.service.repository.system.user.SystemUserRepository;
import com.xiyu.service.util.date.ChineseHoliday;
import com.xiyu.service.util.date.DateUtils;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingQueryMaxLimitOutput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingUpdateMaxLimitInput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingCreateMaxLimitInput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingGetMaxLimitOutput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingGetRuleOutput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingQueryRuleOutput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingUpdateRuleInput;
import com.xiyu.service.vo.setting.poolSetting.PoolSettingCreateRuleInput;
import lombok.extern.slf4j.Slf4j;
import org.babyfish.jimmer.sql.ast.tuple.Tuple2;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

import org.springframework.transaction.annotation.Transactional;
import com.xiyu.service.util.entity.EntityUtils;
import com.xiyu.service.convert.setting.PoolSettingConvert;

import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.errorCode.setting.PoolSettingErrorCode.*;

/**
 * 公海设置 Service 实现类
 */
@Service
@Validated
@Slf4j
public class PoolSettingServiceImpl implements PoolSettingService {

    @Resource
    private CustomerPoolMaxLimitRepository customerPoolMaxLimitRepository;

    @Resource
    private CustomerPoolMaxLimitPostRepository customerPoolMaxLimitPostRepository;

    @Resource
    private CustomerPoolRuleRepository customerPoolRuleRepository;

    @Resource
    private CustomerPoolRuleDeptRepository customerPoolRuleDeptRepository;

    @Resource
    private CustomerPoolRulePostRepository customerPoolRulePostRepository;

    @Resource
    private SystemUserPostRepository systemUserPostRepository;

    @Resource
    private CustomerInfoRepository customerInfoRepository;

    @Resource
    private BusinessFieldService businessFieldService;

    @Resource
    SettingGlobalRepository settingGlobalRepository;

    @Resource
    SystemUserRepository systemUserRepository;

    @Resource
    SystemNoticeRepository systemNoticeRepository;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createRule(PoolSettingCreateRuleInput inputVO) {
        CustomerPoolRule oldRule = customerPoolRuleRepository.findByName(inputVO.getName()).orElse(null);
        if(oldRule != null){
            throw exception(CUSTOMER_POOL_RULE_EXIST);
        }

        CustomerPoolRule newCustomerPoolRule = PoolSettingConvert.INSTANCE.createRuleInputConvert(inputVO);
        newCustomerPoolRule = customerPoolRuleRepository.insert(newCustomerPoolRule);
        CustomerPoolRule finalNewCustomerPoolRule = newCustomerPoolRule;
        if(inputVO.getDeptIds() != null){
            List<CustomerPoolRuleDept> ruleDeptList = new ArrayList<>();
            for(Long deptId : inputVO.getDeptIds()){
                ruleDeptList.add(CustomerPoolRuleDeptDraft.$.produce(draft -> draft.setRuleId(finalNewCustomerPoolRule.id()).setDeptId(deptId)));
            }
            customerPoolRuleDeptRepository.saveAll(ruleDeptList);
        }

        if(inputVO.getPostIds() != null){
            List<CustomerPoolRulePost> rulePostList = new ArrayList<>();
            for(Long postId : inputVO.getPostIds()){
                rulePostList.add(CustomerPoolRulePostDraft.$.produce(draft -> draft.setRuleId(finalNewCustomerPoolRule.id()).setPostId(postId)));
            }
            customerPoolRulePostRepository.saveAll(rulePostList);
        }
        return newCustomerPoolRule.id();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateRule(PoolSettingUpdateRuleInput inputVO) {
        Optional<CustomerPoolRule> optionalOldCustomerPoolRule = customerPoolRuleRepository.findById(inputVO.getId());
        if(optionalOldCustomerPoolRule.isEmpty()){
            throw exception(CUSTOMER_POOL_RULE_NOT_EXIST);
        }
        CustomerPoolRule oldRule = customerPoolRuleRepository.findByName(inputVO.getName()).orElse(null);
        if(oldRule != null && oldRule.id() != inputVO.getId()){
            throw exception(CUSTOMER_POOL_RULE_EXIST);
        }
        CustomerPoolRule updateCustomerPoolRule = PoolSettingConvert.INSTANCE.updateRuleInputConvert(inputVO);
        if (!EntityUtils.isEquals(optionalOldCustomerPoolRule.get(), updateCustomerPoolRule))
            customerPoolRuleRepository.update(updateCustomerPoolRule);

        customerPoolRuleDeptRepository.deleteByRuleAndDept(updateCustomerPoolRule.id(), inputVO.getDeptIds());
        if(inputVO.getDeptIds() != null){
            List<CustomerPoolRuleDept> ruleDeptList = new ArrayList<>();
            for(Long deptId : inputVO.getDeptIds()){
                ruleDeptList.add(CustomerPoolRuleDeptDraft.$.produce(draft -> draft.setRuleId(updateCustomerPoolRule.id()).setDeptId(deptId)));
            }
            customerPoolRuleDeptRepository.saveAll(ruleDeptList);
        }

        customerPoolRulePostRepository.deleteByRuleAndPost(updateCustomerPoolRule.id(), inputVO.getPostIds());
        if(inputVO.getPostIds() != null){
            List<CustomerPoolRulePost> rulePostList = new ArrayList<>();
            for(Long postId : inputVO.getPostIds()){
                rulePostList.add(CustomerPoolRulePostDraft.$.produce(draft -> draft.setRuleId(updateCustomerPoolRule.id()).setPostId(postId)));
            }
            customerPoolRulePostRepository.saveAll(rulePostList);
        }

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteRule(Long id) {
        Optional<CustomerPoolRule> optionalOldCustomerPoolRule = customerPoolRuleRepository.findById(id);
        if(optionalOldCustomerPoolRule.isEmpty()){
            throw exception(CUSTOMER_POOL_RULE_NOT_EXIST);
        }
        customerPoolRuleRepository.deleteById(id);
        return true;
    }

    @Override
    public List<PoolSettingQueryRuleOutput> queryRule() {
        return PoolSettingConvert.INSTANCE.queryRuleOutputConvert(customerPoolRuleRepository.findDetail());
    }

    @Override
    public PoolSettingGetRuleOutput getRule(Long id) {
        Optional<CustomerPoolRule> optionalOldCustomerPoolRule = customerPoolRuleRepository.findById(id);
        if(optionalOldCustomerPoolRule.isEmpty()){
            throw exception(CUSTOMER_POOL_RULE_NOT_EXIST);
        }
        return PoolSettingConvert.INSTANCE.getRuleOutputConvert(customerPoolRuleRepository.findDetailById(id).orElse(null));
    }

    @Override
    public PoolSettingGetMaxLimitOutput getMaxLimit(Long id) {
        Optional<CustomerPoolMaxLimit> optionalCustomerPoolMaxLimit = customerPoolMaxLimitRepository.findById(id);
        if(optionalCustomerPoolMaxLimit.isEmpty()){
            throw exception(CUSOMTER_POOL_MAX_LIMIT_NOT_EXIST);
        }
        return PoolSettingConvert.INSTANCE.getMaxLimitOutputConvert(customerPoolMaxLimitRepository.findDetailById(id).orElse(null));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createMaxLimit(PoolSettingCreateMaxLimitInput inputVO) {
        CustomerPoolMaxLimit newCustomerPoolMaxLimit = PoolSettingConvert.INSTANCE.createMaxLimitInputConvert(inputVO);
        newCustomerPoolMaxLimit = customerPoolMaxLimitRepository.insert(newCustomerPoolMaxLimit);
        List<CustomerPoolMaxLimitPost> limitPosts = new ArrayList<>();
        CustomerPoolMaxLimit finalNewCustomerPoolMaxLimit = newCustomerPoolMaxLimit;
        for(Long postId : inputVO.getPostIds()){
            limitPosts.add(CustomerPoolMaxLimitPostDraft.$.produce(draft -> draft.setLimitId(finalNewCustomerPoolMaxLimit.id()).setPostId(postId)));
        }
        customerPoolMaxLimitPostRepository.saveAll(limitPosts);
        return newCustomerPoolMaxLimit.id();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateMaxLimit(PoolSettingUpdateMaxLimitInput inputVO) {
        Optional<CustomerPoolMaxLimit> optionalOldCustomerPoolMaxLimit = customerPoolMaxLimitRepository.findById(inputVO.getId());
        if(optionalOldCustomerPoolMaxLimit.isEmpty()){
            throw exception(CUSOMTER_POOL_MAX_LIMIT_NOT_EXIST);
        }
        CustomerPoolMaxLimit updateCustomerPoolMaxLimit = PoolSettingConvert.INSTANCE.updateMaxLimitInputConvert(inputVO);
        if (!EntityUtils.isEquals(optionalOldCustomerPoolMaxLimit.get(), updateCustomerPoolMaxLimit))
            customerPoolMaxLimitRepository.update(updateCustomerPoolMaxLimit);
        customerPoolMaxLimitPostRepository.detailByLimitAndPost(updateCustomerPoolMaxLimit.id(), inputVO.getPostIds());
        if(inputVO.getPostIds() != null){
            List<CustomerPoolMaxLimitPost> limitPosts = new ArrayList<>();
            for(Long postId : inputVO.getPostIds()){
                limitPosts.add(CustomerPoolMaxLimitPostDraft.$.produce(draft -> draft.setLimitId(updateCustomerPoolMaxLimit.id()).setPostId(postId)));
            }
            customerPoolMaxLimitPostRepository.saveAll(limitPosts);
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteMaxLimit(Long id) {
        Optional<CustomerPoolMaxLimit> optionalCustomerPoolMaxLimit = customerPoolMaxLimitRepository.findById(id);
        if(optionalCustomerPoolMaxLimit.isEmpty()){
            throw exception(CUSOMTER_POOL_MAX_LIMIT_NOT_EXIST);
        }
        customerPoolMaxLimitRepository.deleteById(id);
        return true;
    }

    @Override
    public List<PoolSettingQueryMaxLimitOutput> queryMaxLimit() {
        return PoolSettingConvert.INSTANCE.queryMaxLimitOutputConvert(customerPoolMaxLimitRepository.findDetail());
    }

    @Override
    public Long getUserAvailableCount(Long ownerId){
        Long stateDealOptionId = businessFieldService.getStateDealOptionId();
        Tuple2<Long, Long> customerCount = customerInfoRepository.countByOwnerId(ownerId, stateDealOptionId);
        List<SystemUserPost> userPostList = systemUserPostRepository.findByUserId(ownerId);
        Long availableCount = null;
        Set<CustomerPoolMaxLimit> maxLimitList = new HashSet<>();
        for(SystemUserPost userPost : userPostList){
            maxLimitList.addAll(customerPoolMaxLimitRepository.findByPostId(userPost.postId()));
        }
        for(CustomerPoolMaxLimit maxLimit : maxLimitList){
            long currentAvailableCount = maxLimit.customerCountLimit() -  (maxLimit.isOnlyNoDeal() ?  customerCount.component2() : customerCount.component1());
            if(availableCount == null){
                availableCount = currentAvailableCount;
            }else{
                availableCount = availableCount > currentAvailableCount ? currentAvailableCount : availableCount;
            }
        }
        return availableCount;
    }

    @Override
    public void ruleExec(){
        log.info("开始执行公海规则");
        SettingGlobal poolEnable = settingGlobalRepository.findBySettingKey("customerPoolEnable").orElse(null);
        if(poolEnable == null || Objects.equals(poolEnable.settingValue(), "false"))
            return;

        // 执行即将掉入公海策略
        comingRuleExec();

        //执行掉入公海策略
        dropRuleExec();

        //执行即将掉入公海通知策略
        noticeRuleExec();
        log.info("结束执行公海规则");
    }

    private void comingRuleExec(){
        LocalDateTime nowTime = LocalDateTime.now();
        SettingGlobal comingFallExecTimeSetting = settingGlobalRepository.findBySettingKey("customerPoolComingFallExecTime").orElse(null);
        if(comingFallExecTimeSetting == null)
            return;

        if(isCurrentDayAlreadyExec(comingFallExecTimeSetting.settingValue())){
            return;
        }

        SettingGlobal tipDaySetting = settingGlobalRepository.findBySettingKey("customerPoolTipDay").orElse(null);
        if(tipDaySetting == null)
            return;

        Long tipDay = Long.parseLong(tipDaySetting.settingValue());

        SettingGlobal dropTimeSetting = settingGlobalRepository.findBySettingKey("customerPoolDropTime").orElse(null);
        if(dropTimeSetting == null)
            return;

        LocalDateTime dropTime = DateUtil.parse(dropTimeSetting.settingValue(), "HH:mm:ss").toLocalDateTime();

        settingGlobalRepository.updateSetting("customerPoolComingFallExecTime", DateUtil.format(nowTime, "yyyy-MM-dd HH:mm:ss"));
        Long stateDealOptionId = businessFieldService.getStateDealOptionId();
        Long lastOwnerId = 0L;
        List<CustomerPoolRule> lastPoolRules = null;
        List<Long> customerIds = customerInfoRepository.findAllNotInPool(stateDealOptionId);
        for(Long customerId : customerIds){
            CustomerInfo customerInfo = customerInfoRepository.findNullable(customerId);
            if(customerInfo == null)
                continue;
            if(!Objects.equals(lastOwnerId, customerInfo.ownerId())){
                lastOwnerId = customerInfo.ownerId();
                lastPoolRules = getOwnerPoolRule(customerInfo.ownerId());
            }

            if(lastPoolRules == null || lastPoolRules.size() == 0)
                continue;

            setComingFallTime(lastPoolRules, customerInfo, tipDay, dropTime);
        }

    }

    private boolean isHolidayToday(){
        LocalDate nowDate = LocalDate.now();
        SettingGlobal skipHolidaySetting = settingGlobalRepository.findBySettingKey("customerPoolSkipHoliday").orElse(null);
        SettingGlobal skipSaturdaySetting = settingGlobalRepository.findBySettingKey("customerPoolSkipSaturday").orElse(null);
        SettingGlobal SkipSundaySetting = settingGlobalRepository.findBySettingKey("customerPoolSkipSunday").orElse(null);
        if(skipHolidaySetting == null || skipSaturdaySetting == null || SkipSundaySetting == null){
            return false;
        }
        boolean isSkipHoliday = Boolean.parseBoolean(skipHolidaySetting.settingValue());
        boolean isSkipSaturday = Boolean.parseBoolean(skipSaturdaySetting.settingValue());
        boolean isSkipSunday = Boolean.parseBoolean(SkipSundaySetting.settingValue());
        if(isSkipHoliday){
            return !ChineseHoliday.isWorkingDay(nowDate, isSkipSaturday, isSkipSunday);
        }else{
            return (isSkipSaturday && nowDate.getDayOfWeek() == DayOfWeek.SATURDAY) || (isSkipSunday && nowDate.getDayOfWeek() == DayOfWeek.SUNDAY);
        }
    }

    private void dropRuleExec(){
        LocalDateTime nowTime = LocalDateTime.now();
        SettingGlobal dropTimeSetting = settingGlobalRepository.findBySettingKey("customerPoolDropTime").orElse(null);
        SettingGlobal dropExecTimeSetting = settingGlobalRepository.findBySettingKey("customerPoolDropExecTime").orElse(null);

        if(dropTimeSetting == null || dropExecTimeSetting == null){
            return;
        }

        // 当天只执行一次
        if(isCurrentDayAlreadyExec(dropExecTimeSetting.settingValue())){
            return;
        }

        // 判断是否到了执行时间
        if(isBeforeExecTime(dropTimeSetting.settingValue()))
            return;

        // 判断是否是节假日
        if(isHolidayToday()){
            return;
        }

        // 更新执行时间
        settingGlobalRepository.updateSetting("customerPoolDropExecTime", DateUtil.format(nowTime, "yyyy-MM-dd HH:mm:ss"));

        // 执行调入公海策略
        Long stateDealOptionId = businessFieldService.getStateDealOptionId();
        List<Long> customerIds = customerInfoRepository.findAllNotInPool(stateDealOptionId);

        for(Long customerId : customerIds){
            CustomerInfo customerInfo = customerInfoRepository.findNullable(customerId);
            if(customerInfo == null)
                continue;
            if(customerInfo.comingFallPoolRuleId() == null || customerInfo.comingFallPoolRuleId() == 0L)
                continue;
            if(customerInfo.comingFallPoolTime() == null)
                continue;
            if(customerInfo.comingFallPoolTime().isAfter(nowTime))
                continue;

            CustomerPoolRule matchRule = customerPoolRuleRepository.findNullable(customerInfo.comingFallPoolRuleId());
            if(matchRule == null)
                continue;

            customerInfoRepository.updateFallInPool(customerId, "根据规则：" + matchRule.name() + " 自动掉入公海");
        }

    }

    private List<CustomerPoolRule> getOwnerPoolRule(Long ownerId){
        SystemUser owner = systemUserRepository.findNullable(ownerId);
        if(owner == null)
            return null;
        return customerPoolRuleRepository.findMatchRule(owner.deptId(), owner.postIds());
    }

    private void setComingFallTime(List<CustomerPoolRule> poolRules, CustomerInfo customerInfo, Long tipDay, LocalDateTime dropTime){
        LocalDateTime comingFallPoolTime = null;
        Long matchRuleId = null;
        LocalDateTime nowTime = LocalDate.now().atTime(0, 0,0);
        for(CustomerPoolRule poolRule : poolRules){
            LocalDateTime followFallPoolTime = null;
            if(poolRule.lastFollowDay() > 0){
                LocalDateTime lastFollowTime = customerInfo.lastFollowTime() == null ? customerInfo.createTime() : customerInfo.lastFollowTime();
                if(lastFollowTime != null && lastFollowTime.plusDays(poolRule.lastFollowDay()).minusDays(tipDay).isBefore(nowTime)){
                    followFallPoolTime = lastFollowTime.plusDays(poolRule.lastFollowDay()).withHour(dropTime.getHour()).withMinute(dropTime.getMinute()).withSecond(dropTime.getSecond());
                }
            }

            LocalDateTime sendFallPoolTime = null;
            if(poolRule.lastSendMailDay() > 0){
                LocalDateTime lastSendMailTime = customerInfo.lastSendMailTime() == null ? customerInfo.createTime() : customerInfo.lastSendMailTime();
                if(lastSendMailTime != null && lastSendMailTime.plusDays(poolRule.lastSendMailDay()).minusDays(tipDay).isBefore(nowTime)){
                    sendFallPoolTime = lastSendMailTime.plusDays(poolRule.lastSendMailDay()).withMinute(dropTime.getMinute()).withSecond(dropTime.getSecond());
                }
            }

            comingFallPoolTime = DateUtils.min(comingFallPoolTime, followFallPoolTime);
            comingFallPoolTime = DateUtils.min(comingFallPoolTime, sendFallPoolTime);
            if(comingFallPoolTime != null && (comingFallPoolTime.equals(followFallPoolTime) || comingFallPoolTime.equals(sendFallPoolTime)))
                matchRuleId = poolRule.id();
        }
        if(matchRuleId != null)
            customerInfoRepository.updateComingFallTime(customerInfo.id(), comingFallPoolTime, matchRuleId);

    }

    private boolean isBeforeExecTime(String time){
        if(time == null || time.equals(""))
            return true;
        LocalTime localTime = DateUtil.parse(time,"HH:mm:ss").toLocalDateTime().toLocalTime();
        return LocalTime.now().isBefore(localTime);
    }

    private void noticeRuleExec(){
        LocalDateTime nowTime = LocalDateTime.now();
        SettingGlobal tipDaySetting = settingGlobalRepository.findBySettingKey("customerPoolTipDay").orElse(null);
        SettingGlobal tipTimeSetting = settingGlobalRepository.findBySettingKey("customerPoolTipTime").orElse(null);
        SettingGlobal noticeExecTimeSetting = settingGlobalRepository.findBySettingKey("customerPoolNoticeExecTime").orElse(null);
        if(tipDaySetting == null || tipTimeSetting == null || noticeExecTimeSetting == null){
            return;
        }
        long tipDay = Long.parseLong(tipDaySetting.settingValue());

        // 当天只执行一次
        if(isCurrentDayAlreadyExec(noticeExecTimeSetting.settingValue())){
            return;
        }

        // 判断是否到了执行时间
        if(isBeforeExecTime(tipTimeSetting.settingValue()))
            return;

        // 更新执行时间
        settingGlobalRepository.updateSetting("customerPoolNoticeExecTime", DateUtil.format(nowTime, "yyyy-MM-dd HH:mm:ss"));

        // 执行通知策略
        Long stateDealOptionId = businessFieldService.getStateDealOptionId();
        List<Long> customerIds = customerInfoRepository.findAllNotInPool(stateDealOptionId);

        Map<Long, Long> ownerCustomerCount = new HashMap<>();
        for(Long customerId : customerIds){
            CustomerInfo customerInfo = customerInfoRepository.findNullable(customerId);
            if(customerInfo == null)
                continue;
            if(customerInfo.comingFallPoolRuleId() == null || customerInfo.comingFallPoolRuleId() == 0L)
                continue;
            LocalDateTime comingFallPoolTime = customerInfo.comingFallPoolTime();
            if(comingFallPoolTime == null)
                continue;
            LocalDateTime comingFallPollDay = DateUtils.of(DateUtils.buildTime(comingFallPoolTime.getYear(), comingFallPoolTime.getMonthValue(), comingFallPoolTime.getHour()));
            if(comingFallPollDay.minusDays(tipDay).isAfter(nowTime))
                continue;

            CustomerPoolRule matchRule = customerPoolRuleRepository.findNullable(customerInfo.comingFallPoolRuleId());
            if(matchRule == null)
                continue;

            ownerCustomerCount.put(customerInfo.ownerId(), ownerCustomerCount.getOrDefault(customerInfo.ownerId(), 0L) + 1);

        }
        for(Long ownerId : ownerCustomerCount.keySet()){
            SystemNotice notice = SystemNoticeDraft.$.produce(draft -> draft.setIsRead(false).setContent("您有" + ownerCustomerCount.get(ownerId) + "个客户即将掉入公海，请及时跟进处理")
                    .setNotifierId(ownerId).setTitle("").setStatus(0).setType(SystemNoticeTypeEnum.POOL.getValue()));
            systemNoticeRepository.insert(notice);
        }

    }

    // 判断当天是否已经执行
    private boolean isCurrentDayAlreadyExec(String execTime){
        return !Objects.equals(execTime, "") && DateUtils.isToday(DateUtil.parseLocalDateTime(execTime, "yyyy-MM-dd HH:mm:ss"));
    }


}
